package com.it.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.it.enums.ErrorCode;
import com.it.constant.UserConstant;
import com.it.exception.BusinessException;
import com.it.model.entity.User;
import com.it.service.UserService;
import com.it.mapper.UserMapper;
import com.it.utils.AccountValidator;
import com.it.utils.AlgorithmUtils;
import io.swagger.models.auth.In;
import javafx.util.Pair;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author zsh
 * @description 针对表【user(用户中心项目的用户表)】的数据库操作Service实现
 * @createDate 2025-04-12 22:02:33
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

  /** 拼接密码前缀,加密更安全 */
  public static final String Extra_Password_Str = "zsh-coding";

  @Resource private UserMapper userMapper;

  @Resource private RedisTemplate<String, Object> redisTemplate;

  /**
   * 用户注册
   *
   * @param loginAccount
   * @param loginPassword
   * @param checkPassword
   * @return
   */
  public Long userRegister(String loginAccount, String loginPassword, String checkPassword) {
    // 校验参数是否为空
    if (StrUtil.hasEmpty(loginAccount, loginPassword, checkPassword)) {
      throw new BusinessException(ErrorCode.NULL_ERROR, "请求注册的参数不能为空...");
    }
    // 账号不低于4位
    if (loginAccount.length() < 4) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "注册的账号长度不能低于4位...");
    }

    //  密码不低于6位
    if (checkPassword.length() < 6 || loginPassword.length() < 6) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "注册的密码和校验密码长度都不能低于6位...");
    }

    // 账号不能有特殊字符
    if (!AccountValidator.isValidAccount(loginAccount)) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "注册的账号不能有特殊字符,只允许字母、数字和下划线...");
    }

    // 密码和校验密码要一样
    if (!checkPassword.equals(loginPassword)) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "注册的密码和校验密码要一致...");
    }

    // 账号不能重复, 所以要查询一下数据库
    LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<User>();
    userLambdaQueryWrapper.eq(User::getLoginAccount, loginAccount);

    User oneUser = this.getOne(userLambdaQueryWrapper);
    if (oneUser != null) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "该账号已被注册...");
    }

    // 上面校验都通过了,现在新增数据/
    // 先对密码加密
    String encryptPassword = DigestUtil.md5Hex(Extra_Password_Str + loginPassword);
    User user = new User();
    user.setLoginAccount(loginAccount);
    user.setLoginPassword(encryptPassword);
    user.setUserRole(UserConstant.ADMIN_ROLE);
    boolean flag = this.save(user);
    if (!flag) {
      throw new BusinessException(ErrorCode.SYSTEM_ERROR, "账号注册失败,系统内部异常...");
    }
    return 1L;
  }

  /**
   * 用户登录
   *
   * @param loginAccount
   * @param loginPassword
   * @return
   */
  public User userLogin(String loginAccount, String loginPassword, HttpServletRequest request) {

    // 校验参数是否为空
    if (StrUtil.hasEmpty(loginAccount, loginPassword)) {
      throw new BusinessException(ErrorCode.NULL_ERROR, "请求登录的参数不能为空...");
    }

    // 账号不低于4位
    if (loginAccount.length() < 4) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "登录的账号长度不能低于4位...");
    }

    //  密码不低于6位
    if (loginPassword.length() < 6) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "登录的密码长度不能低于6位...");
    }

    // 账号不能有特殊字符
    if (!AccountValidator.isValidAccount(loginAccount)) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "登录的账号不能有特殊字符,只允许字母、数字和下划线...");
    }

    // 先对传入的密码加密,再和数据库中的密码对比
    String encryptPassword = DigestUtil.md5Hex(Extra_Password_Str + loginPassword);

    // 查询数据库是否存在该用户
    LambdaQueryWrapper<User> userLoginRequestLambdaQueryWrapper = new LambdaQueryWrapper<>();
    userLoginRequestLambdaQueryWrapper
        .eq(User::getLoginAccount, loginAccount)
        .eq(User::getLoginPassword, encryptPassword);

    List<User> users = this.list(userLoginRequestLambdaQueryWrapper);

    // 用户不存在
    if (users.size() == 0) {
      log.info("user login failed, userAccount cannot match userPassword");
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "该账号用户不存在,可能是因为账号或密码输入错误...");
    }
    // 用户存在
    // 进行信息的脱敏
    User user = users.get(0);
    User safeUser = getSafeUser(user);

    // 进行在服务器中保存当前用户登录状态
    request.getSession().setAttribute(UserConstant.USER_LOGIN_STATE, safeUser);
    return safeUser;
  }

  /**
   * 管理员根据昵称模糊查询账号人员信息
   *
   * @param username
   * @param request
   * @return
   */
  @Override
  public List<User> searchUsers(String username, HttpServletRequest request) {
    //  校验身份 --- 是不是管理员
    if (!isAdmin(request)) {
      throw new BusinessException(ErrorCode.NO_AUTH, "该账号不是管理员...");
    }

    //  - 模糊查询获取信息
    LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
    userLambdaQueryWrapper.like(User::getNickname, username);
    List<User> userList = this.list(userLambdaQueryWrapper);

    //        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
    //        userQueryWrapper.like("nickname", username);
    //
    //        List<User> userList = this.list(userQueryWrapper);

    //  - 信息脱敏
    List<User> safeUseList =
        userList.stream()
            .map(
                (user -> {
                  User safeUser = getSafeUser(user);
                  return safeUser;
                }))
            .collect(Collectors.toList());

    // - 返回脱敏后的数据
    return safeUseList;
  }

  /** 用户推荐页 */
  @Override
  public List<User> recommendUsers(Long pageNum, Long pageSize, HttpServletRequest request) {
    if (pageNum == null || pageSize == null) {
      throw new BusinessException(ErrorCode.NULL_ERROR, "请求参数页码为空...");
    }

    // 获取当前登录用户的登录信息
    User loginUser = this.getCurrentLoginUser(request);
    String redisKey =
        String.format(UserConstant.Ahead_User_Recommend_Str + ":%s", loginUser.getId());
    System.out.println("当前redis缓存key为" + redisKey);

    // 1. 查缓存 redis
    // 2. 缓存中有返回page对象
    ValueOperations<String, Object> stringObjectValueOperations = redisTemplate.opsForValue();
    Page<User> usersPage = (Page<User>) stringObjectValueOperations.get(redisKey);

    if (usersPage != null) {
      System.out.println("当前缓存中存在数据,直接返回...");
      List<User> usersPageRecords = usersPage.getRecords();
      return usersPageRecords;
    }

    System.out.println("当前缓存中没有数据,现在要从数据库中查找...");
    // 3. 缓存中没有就查数据库
    QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();

    usersPage = new Page<>(pageNum, pageSize);

    this.page(usersPage, userQueryWrapper);
    List<User> usersPageRecords = usersPage.getRecords();

    // 4. 再向redis中插入数据
    try {
      System.out.println("即将往redis缓存中设置数据");
      // 设置过期时间 30s
      // TODO 后期过期时间再设置一下
      stringObjectValueOperations.set(
          redisKey, usersPage, 30000, TimeUnit.MILLISECONDS); // , 30000, TimeUnit.MICROSECONDS
    } catch (Exception e) {
      log.error("缓存设置用户信息失败...");
    }

    // 5. 返回前端页面前,进行数据脱敏
    List<User> resultList =
        usersPageRecords.stream()
            .map(
                user -> {
                  return this.getSafeUser(user);
                })
            .collect(Collectors.toList());

    return resultList;
  }

  /**
   * 根据id进行逻辑删除
   *
   * @param userId
   * @param request
   * @return
   */
  public boolean deleteUserById(Integer userId, HttpServletRequest request) {
    //  校验身份 --- 是不是管理员
    if (!isAdmin(request)) {
      throw new BusinessException(ErrorCode.NO_AUTH, "该账号不是管理员...");
    }
    if (userId <= 0) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求的参数id不能小于等于0");
    }
    return this.removeById(userId);
  }

  /**
   * 进行信息脱敏
   *
   * @param originUser
   * @return
   */
  @Override
  public User getSafeUser(User originUser) {
    if (originUser == null) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "方法传递的参数不能为空...");
    }
    User safeUser = new User();
    safeUser.setId(originUser.getId());
    safeUser.setNickname(originUser.getNickname());
    safeUser.setGender(originUser.getGender());
    safeUser.setAvatarUrl(originUser.getAvatarUrl());
    safeUser.setLoginAccount(originUser.getLoginAccount());
    safeUser.setUserStatus(originUser.getUserStatus());
    safeUser.setPhone(originUser.getPhone());
    safeUser.setCreateTime(originUser.getCreateTime());
    safeUser.setUpdateTime(originUser.getUpdateTime());
    safeUser.setDeleteFlag(originUser.getDeleteFlag());
    safeUser.setUserRole(originUser.getUserRole());
    safeUser.setTags(originUser.getTags());
    safeUser.setUserInfo(originUser.getUserInfo());

    /**
     * safetyUser.setId(originUser.getId()); safetyUser.setUsername(originUser.getUsername());
     * safetyUser.setUserAccount(originUser.getUserAccount());
     * safetyUser.setAvatarUrl(originUser.getAvatarUrl());
     * safetyUser.setGender(originUser.getGender()); safetyUser.setPhone(originUser.getPhone());
     * safetyUser.setEmail(originUser.getEmail());
     * safetyUser.setPlanetCode(originUser.getPlanetCode());
     * safetyUser.setUserRole(originUser.getUserRole());
     * safetyUser.setUserStatus(originUser.getUserStatus());
     * safetyUser.setCreateTime(originUser.getCreateTime());
     * safetyUser.setTags(originUser.getTags());
     */
    return safeUser;
  }

  /**
   * 更新用户信息
   *
   * @param user
   * @return
   */
  @Override
  public int updateUser(User user, User loginUser) {
    long userId = user.getId();
    if (userId <= 0) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR);
    }
    // todo 补充校验，如果用户没有传任何要更新的值，就直接报错，不用执行 update 语句
    // 如果是管理员，允许更新任意用户
    // 如果不是管理员，只允许更新当前（自己的）信息
    if (!isAdmin(loginUser) && userId != loginUser.getId()) {
      throw new BusinessException(ErrorCode.NO_AUTH);
    }
    User oldUser = userMapper.selectById(userId);
    if (oldUser == null) {
      throw new BusinessException(ErrorCode.NULL_ERROR);
    }
    return userMapper.updateById(user);
  }

  /**
   * 获取当前登录用户信息
   *
   * @return
   */
  @Override
  public User getCurrentLoginUser(HttpServletRequest request) {
    Object userObj = request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE);
    User currentUser = (User) userObj;
    if (currentUser == null) {
      throw new BusinessException(ErrorCode.NOT_LOGIN);
    }
    long userId = currentUser.getId();
    User user = this.getById(userId);
    User safetyUser = this.getSafeUser(user);
    return safetyUser;
  }

  /**
   * 进行判断是否当前用户是管理员
   *
   * @param request
   * @return
   */
  public boolean isAdmin(HttpServletRequest request) {
    Object attribute = request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE);
    User user = (User) attribute;
    if (user == null || user.getUserRole() != UserConstant.ADMIN_ROLE) return false;
    return true;
  }

  /**
   * 是否为管理员
   *
   * @param loginUser
   * @return
   */
  @Override
  public boolean isAdmin(User loginUser) {
    return loginUser != null && loginUser.getUserRole() == UserConstant.ADMIN_ROLE;
  }

  /**
   * 用户退出
   *
   * @param request
   */
  @Override
  public Integer userLogout(HttpServletRequest request) {
    // 移除登录态
    request.getSession().removeAttribute(UserConstant.USER_LOGIN_STATE);
    return 1;
  }

  /**
   * 根据标签查询用户,先在mysql层把所有用户都查出来,在Java代码层面进行过滤,最后再返回
   *
   * @param tagNameList
   * @return
   */
  @Override
  public List<User> searchUsersByTags(List<String> tagNameList) {
    ///////  第二种方式查询    ////////
    // 先查询所有用户信息
    QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
    List<User> userList = this.list(userQueryWrapper);

    Gson gson = new Gson();

    List<User> resultUserList =
        userList.stream()
            .filter(
                user -> {
                  String tags = user.getTags();
                  Set<String> tagNameSet = new HashSet<>();
                  if (tags != null) {
                    Type type = new TypeToken<Set<String>>() {}.getType();
                    tagNameSet = gson.fromJson(tags, type);
                  }
                  if (tagNameSet == null) {
                    return false;
                  }
                  for (String tagName : tagNameList) {
                    if (!tagNameSet.contains(tagName)) {
                      return false;
                    }
                  }
                  return true;
                })
            .map(
                user -> {
                  return this.getSafeUser(user);
                })
            .collect(Collectors.toList());

    return resultUserList;
  }

  @Override
  public List<User> matchUsers(long num, User loginUser) {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.select("id", "tags");
    queryWrapper.isNotNull("tags");
    List<User> userList = this.list(queryWrapper);
    String tags = loginUser.getTags();
    Gson gson = new Gson();
    List<String> tagList = gson.fromJson(tags, new TypeToken<List<String>>() {}.getType());
    // 用户列表的下标 => 相似度
    List<Pair<User, Long>> list = new ArrayList<>();
    // 依次计算所有用户和当前用户的相似度
    for (int i = 0; i < userList.size(); i++) {
      User user = userList.get(i);
      String userTags = user.getTags();
      // 无标签或者为当前用户自己
      if (StrUtil.isBlank(userTags) || user.getId() == loginUser.getId()) {
        continue;
      }
      List<String> userTagList =
          gson.fromJson(userTags, new TypeToken<List<String>>() {}.getType());
      // 计算分数
      long distance = AlgorithmUtils.minDistance(tagList, userTagList);
      list.add(new Pair<>(user, distance));
    }
    // 按编辑距离由小到大排序
    List<Pair<User, Long>> topUserPairList =
        list.stream()
            .sorted((a, b) -> (int) (a.getValue() - b.getValue()))
            .limit(num)
            .collect(Collectors.toList());
    // 原本顺序的 userId 列表
    List<Integer> userIdList =
        topUserPairList.stream().map(pair -> pair.getKey().getId()).collect(Collectors.toList());
    QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
    userQueryWrapper.in("id", userIdList);
    // 1, 3, 2
    // User1、User2、User3
    // 1 => User1, 2 => User2, 3 => User3
    Map<Integer, List<User>> userIdUserListMap =
        this.list(userQueryWrapper).stream()
            .map(
                user -> {
                  return getSafeUser(user);
                })
            .collect(Collectors.groupingBy(User::getId));
    List<User> finalUserList = new ArrayList<>();
    for (Integer userId : userIdList) {
      finalUserList.add(userIdUserListMap.get(userId).get(0));
    }
    return finalUserList;
  }

  /**
   * 修改密码
   *
   * @param oldPassword
   * @param newPassword
   * @param newCheckPassword
   * @param loginUser
   * @return
   */
  @Override
  public Integer resetPassword(
      String oldPassword, String newPassword, String newCheckPassword, User loginUser) {
    //  密码不低于6位
    if (oldPassword.length() < 6) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度不能低于6位...");
    }
    if (newPassword.length() < 6) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度不能低于6位...");
    }
    if (newCheckPassword.length() < 6) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度不能低于6位...");
    }

    // 新密码要和确认密码一致
    if (!(newCheckPassword.equals(newPassword))) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "新密码和确认密码不一致");
    }

    // 旧密码要和之前的一样
    // 先加密再比较
    String encryptPassword = DigestUtil.md5Hex(Extra_Password_Str + oldPassword);
    System.out.println("加密后的密码: " + encryptPassword);

    Integer userId = loginUser.getId();
    LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
    userLambdaQueryWrapper.eq(User::getId, userId);
    List<User> userList = this.list(userLambdaQueryWrapper);
    User u = userList.get(0);

    if (!(u.getLoginPassword().equals(encryptPassword))) {
      throw new BusinessException(ErrorCode.PARAMS_ERROR, "旧密码输入错误~~~");
    }
    // 5358b0b498c7aa2b3ed25383d41cee0b

    // 前面的校验都结束,把新密码加密后存入数据库
    User user = new User();
    user.setId(userId);
    System.out.println("当前密码用户id" + userId);
    user.setLoginPassword(encryptPassword);
    System.out.println("检查一下是否设置密码成功" + user.getLoginPassword());
    boolean flag = this.updateById(user);
    if (!flag) {
      throw new BusinessException(ErrorCode.SYSTEM_ERROR, "数据插入失败,系统异常");
    }
    return 1;
  }

  /**
   * 更新用户标签
   *
   * @param tags
   * @param userId
   */
  @Override
  public void updateTags(List<String> tags, Integer userId) {
    User user = new User();
    Gson gson = new Gson();
    String tagsJson = gson.toJson(tags);
    user.setId(userId);
    user.setTags(tagsJson);
    this.updateById(user);
  }

  /**
   * 获取当前用户的标签列表
   *
   * @param id
   * @return
   */
  @Override
  public List<String> getUserTags(Integer id) {
    User user = this.getById(id);
    String userTags = user.getTags();
    Gson gson = new Gson();
    return gson.fromJson(userTags, new TypeToken<List<String>>() {}.getType());
  }

  /**
   * 根据标签查询用户,先在mysql层写模糊查询,查到数据再返回
   *
   * @param tagNameList
   * @return
   */
  @Deprecated
  public List<User> searchUsersByTagsBySQL(List<String> tagNameList) {
    if (CollUtil.isEmpty(tagNameList)) {
      throw new BusinessException(ErrorCode.NULL_ERROR, "请求的参数标签为空...");
    }

    LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();

    for (String tagName : tagNameList) {
      userLambdaQueryWrapper = userLambdaQueryWrapper.like(User::getTags, tagName);
    }
    List<User> userList = this.list(userLambdaQueryWrapper);

    List<User> resultUserList =
        userList.stream()
            .map(
                s -> {
                  return this.getSafeUser(s);
                })
            .collect(Collectors.toList());

    return resultUserList;
  }
}
